require "set"
require "wondercap/lib/deployment/core/strategy_base"
require "wondercap/lib/deployment/core/versioned_strategy"

module Wondercap
module Deployment

  module InnerConfigurationMixin
    def self.included(base)
      base.send :alias_method, :task_without_inner_configuration_mixin, :task
      base.send :alias_method, :task, :task_with_inner_configuration_mixin
      
      base.send :alias_method, :set_without_inner_configuration_mixin, :set
      base.send :alias_method, :set, :set_with_inner_configuration_mixin
    end
    
    class ForClause
      def initialize(obj, extension)
        @obj = obj
        @extension = extension
      end
      
      def for(substrategy, &block)
        @obj.extend_substrategy(substrategy.to_sym, @extension.to_sym, block)
      end
    end
    
    def use(extension)
      ForClause.new(self, extension)
    end

    def extensions_info
      @extensions_info ||= {}
    end
    
    def extension_contexts
      @extension_contexts ||= []
    end
    attr :current_extension_context, true
    attr :current_unmangled_task, true
    attr :current_unmangled_var, true
    
    class ExtensionContext
      attr_reader :substrategy, :extension
      attr_reader :tasks, :vars
      attr_accessor :configuration_block_arg, :configuration_block, :index
      
      def initialize(substrategy, extension, configuration_block, configuration_block_arg)
        @substrategy, @extension = substrategy.to_s, extension.to_s
        
        @configuration_block = configuration_block
        @configuration_block_arg = configuration_block_arg
        
        @tasks, @vars = {}, {}
      end
    end
      
    def extend_substrategy(substrategy, extension, configuration_block = nil)
      (extensions_info[substrategy] ||= []) << extension

      ext_block, ext_options = get_substrategy_extension(substrategy, extension)
      context = ExtensionContext.new(substrategy, extension, configuration_block, ext_options[:conf_arg])
      
      prev_context = current_extension_context
      self.current_extension_context = context
      instance_eval(&ext_block)
      self.current_extension_context = prev_context
      
      context.index = extension_contexts.length
      extension_contexts << context
    end
    
    def wrap_block_with_context(block, context)
      conf = self
      lambda do
        prev_context = conf.current_extension_context
        conf.current_extension_context = context
        
        if context.configuration_block
          if context.configuration_block_arg.respond_to?(:call)
            context.configuration_block_arg = context.configuration_block_arg.call
          end
          
          #TODO: cleaner implementation (http://blogger.jayfields.com/2006/09/ruby-instanceexec-aka-instanceeval.html)
          class << self; self; end.send :define_method, :_define_single, &context.configuration_block
          send :_define_single, context.configuration_block_arg
          class << self; self; end.send :remove_method, :_define_single
          
          context.configuration_block = nil
        end
        set_without_inner_configuration_mixin :conf_arg, context.configuration_block_arg
        result = instance_eval(&block)
        
        conf.current_extension_context = prev_context
        result
      end
    end
    
    @@mangle_counter = 0
    def task_with_inner_configuration_mixin(name, *args, &block)
      if current_extension_context
        mangled_name = "#{name.to_s}_#{current_extension_context.substrategy}_#{current_extension_context.extension}_#{@@mangle_counter}"
        @@mangle_counter = @@mangle_counter + 1
        task_without_inner_configuration_mixin(mangled_name, *args, &wrap_block_with_context(block, current_extension_context))
        current_extension_context.tasks[name.to_sym] = mangled_name.to_sym
        
        unless find_task(name)
          
          if name.to_s !~ /^_/
            task_without_inner_configuration_mixin name do
              prev_current_unmangled_task = self.current_unmangled_task
              self.current_unmangled_task = name
              begin
                extension_contexts.reverse_each do |ctx|
                  mangled_name = ctx.tasks[name.to_sym]
                  if mangled_name
                    find_and_execute_task(mangled_name)
                    break
                  end
                end

                raise NameError, "no task #{name} found" unless mangled_name
              ensure
                self.current_unmangled_task = prev_current_unmangled_task
              end
            end
          else
            task_without_inner_configuration_mixin name do
              prev_current_unmangled_task = self.current_unmangled_task
              self.current_unmangled_task = name
              
              begin
                mangled_name = current_extension_context.tasks[name.to_sym]
                raise NameError, "there is no private task #{name}" unless mangled_name
                find_and_execute_task(mangled_name)
              ensure
                self.current_unmangled_task = prev_current_unmangled_task
              end
            end
          end
          
        end
      else
        task_without_inner_configuration_mixin(name, *args, &block)
      end
    end
    
    def set_with_inner_configuration_mixin(name, *args, &block)
      if current_extension_context
        mangled_name = "#{name.to_s}_#{current_extension_context.substrategy}_#{current_extension_context.extension}_#{@@mangle_counter}"
        @@mangle_counter = @@mangle_counter + 1
        
        var_block = lambda do
          if block
            instance_eval(&block)
          else
            args.first
          end          
        end
        set_without_inner_configuration_mixin(mangled_name, &wrap_block_with_context(var_block, current_extension_context))
        current_extension_context.vars[name.to_sym] = mangled_name.to_sym
        
        unless exists?(name)
          
          if name.to_s !~ /^_/
            set_without_inner_configuration_mixin name do
              prev_current_unmangled_var = self.current_unmangled_var
              self.current_unmangled_var = name
              begin
                found = false
                result = nil
                extension_contexts.reverse_each do |ctx|
                  mangled_name = ctx.vars[name.to_sym]
                  if mangled_name
                    result = fetch(mangled_name)
                    found = true
                    break
                  end
                end

                raise NameError, "no variable #{name} found" unless found
                result
              ensure
                self.current_unmangled_var = prev_current_unmangled_var
              end
            end
          else
            set_without_inner_configuration_mixin name do
              prev_current_unmangled_var = self.current_unmangled_var
              self.current_unmangled_var = name
              
              begin
                mangled_name = current_extension_context.vars[name.to_sym]
                raise NameError, "there is no private var #{name}" unless mangled_name
                fetch(mangled_name)
              ensure
                self.current_unmangled_var = prev_current_unmangled_var
              end
            end
          end
          
        end
      else
        set_without_inner_configuration_mixin(name, *args, &block)
      end
    end
   
    def execute_previous
      raise "no previous context available" unless current_extension_context
      
      extension_contexts[0...current_extension_context.index].reverse_each do |ctx|
        mangled_name = ctx.tasks[current_unmangled_task]
        if mangled_name
          return find_and_execute_task(mangled_name)
        end
      end
      raise "no previous task for #{current_unmangled_task} found"
    end
    
    def fetch_previous
      raise "no previous context available" unless current_extension_context
      
      extension_contexts[0...current_extension_context.index].reverse_each do |ctx|
        mangled_name = ctx.vars[current_unmangled_var]
        if mangled_name
          return fetch(mangled_name)
        end
      end
      raise "no previous var for #{current_unmangled_var} found"
    end
    
    def use_configuration_value
      prev_defined_var = variables[current_unmangled_var]
      if prev_defined_var
        return prev_defined_var.respond_to?(:call) ? prev_defined_var.call : prev_defined_var
      end
      raise "no variable defined in configuration for #{current_unmangled_var} found"       
    end
   
    def use_template(name, options = {})
      outer_configuration.apply_template(name, self, options)
    end

    def get_substrategy_extension(substrategy, extension)
      outer_configuration.get_substrategy_extension(substrategy, extension)
    end
  
  end
end
end

